package com.gitee.wsl.common.ui.ext.path

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathOperation
import com.gitee.wsl.common.ui.base.Point
import com.gitee.wsl.math.radiansToDegrees
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sign
import kotlin.math.sin



fun Path.set(other : Path){
    reset()
    addPath(other)
}
//internal expect fun Path.set(other : Path)


expect fun Path.addPath(path: Path, matrix: Matrix) : Path

fun Path.moveTo(offset: Offset) {
    moveTo(offset.x, offset.y)
}

fun Path.lineTo(offset: Offset) {
    lineTo(offset.x, offset.y)
}

fun Path.arcTo(lastX: Float,
               lastY: Float,
               cpX: Float,
               cpY: Float,
               x: Float,
               y: Float,
               radius: Float) {
    require(radius >= .0) { "Negative radius:$radius" }
    val alpha1 = atan2(lastY - cpY, lastX - cpX)
    val alpha2 = atan2(y - cpY, x - cpX)
    val alpha = angle(alpha1 - alpha2)
    val d = radius / sin(alpha / 2).absoluteValue
    val cx = cpX + d * cos(alpha1 - alpha / 2)
    val cy = cpY + d * sin(alpha1 - alpha / 2)

    val clockwise = alpha > 0
    val startAngle = alpha1.radToDeg + clockwise.toSign() * 90f
    val sweepAngle =
        if (clockwise) {
            360 - ((180f + alpha.radToDeg) % 360f)
        } else {
            (-180f - alpha.radToDeg) % 360f
        }

    moveTo(lastX, lastY)
    val rect = Rect(
        (cx - radius),
        (cy - radius),
        (cx + radius),
        (cy + radius)
    )

    arcTo(
        rect,
        startAngle,
        sweepAngle,
        false
    )
    lineTo(x, y)
}

fun Path.arcTo(last: Point,
               cp: Point,
               current: Point,
               radius: Float) = arcTo(last.x,last.y,cp.x,cp.y,current.x,current.y,radius)

/**
 * Checks if two paths overlap by performing an intersection operation.
 * If the intersection result is not empty, it returns true, indicating that the paths overlap.
 *
 * @param path1 The first path to check.
 * @param path2 The second path to check.
 * @return `true` if the paths overlap, `false` otherwise.
 */

fun Path.Companion.overlaps(path1: Path, path2: Path): Boolean = Path().apply {
    this.op(path1, path2, PathOperation.Intersect)
}.isEmpty.not()

internal fun angle(alpha: Float): Float = when {
    alpha > PI -> (alpha - 2 * PI).toFloat()
    alpha < -PI -> (2 * PI + alpha).toFloat()
    else -> alpha
}

internal val Number.radToDeg: Float
    get() = (this.toFloat() * 180 / PI).toFloat()

private fun Boolean.toSign(): Int = if (this) 1 else -1

fun Path.arc(
    centerX: Float,
    centerY: Float,
    radius: Float,
    startAngle: Float,
    endAngle: Float,
    counterClockWise:Boolean = false
) {
    require(radius >= .0) { "Negative radius:$radius" }

    val rect = Rect(left = (centerX - radius),
            top = (centerY - radius),
            right = (centerX + radius),
            bottom = (centerY + radius))


    val startAngle = startAngle.radiansToDegrees()
    var sweepAngle = endAngle.radiansToDegrees() - startAngle

    // translating the rotation to an angle value
    if (!counterClockWise && sweepAngle < -EPSILON_FLOAT) sweepAngle = (sweepAngle % 360) + 360
    if (counterClockWise && sweepAngle > EPSILON_FLOAT) sweepAngle = (sweepAngle % 360) - 360

    // on Android an arc with an angle of 360 is not drawn !
    // Android doc:
    // sweepAngle	float: Sweep angle (in degrees) measured clockwise, treated mod 360.
    if (abs(sweepAngle) >= EPSILON_CIRCLE && abs(sweepAngle % 360) <= EPSILON_FLOAT) {
        sweepAngle = EPSILON_CIRCLE * sweepAngle.sign
    }

    arcTo(rect, startAngle, sweepAngle,false)
}

fun Path.arc(
    center: Point,
    radius: Float,
    startAngle: Float,
    endAngle: Float,
    counterClockWise:Boolean = false
) = arc(center.x,center.y,radius,startAngle,endAngle,counterClockWise)



private const val EPSILON_FLOAT = 0.0001f
private const val EPSILON_CIRCLE = 360 - EPSILON_FLOAT

fun Path.horizontalCubicTo(
    prev: Point,
    current: Point,
    curvature: Float
) = horizontalCubicTo(prev.x,prev.y,current.x,current.y,curvature)

fun Path.horizontalCubicTo(
    prevX: Float,
    prevY: Float,
    x: Float,
    y: Float,
    curvature: Float
) {
    val directionMultiplier = if (x >= prevX) 1f else -1f
    cubicTo(prevX + directionMultiplier * curvature, prevY, x - directionMultiplier * curvature, y, x, y)
}


fun Path.quadraticCurveTo(cpx: Float, cpy: Float, x: Float, y: Float){
    this.quadraticBezierTo(cpx,cpy,x,y)
}

fun Path.bezierCurveTo(cpx1: Float, cpy1: Float, cpx2: Float, cpy2: Float, x: Float, y: Float){
    this.cubicTo(cpx1,cpy1,cpx2,cpy2,x,y)
}


/*
fun Path.scaleX(scale: Float, px: Float, py: Float) {
    val matrix = Matrix()
    matrix.scale(scale, 1f, px, py)
    transform(matrix)
}

fun Path.scaleY( scale: Float, px: Float, py: Float) {
    val matrix = Matrix()
    matrix.scale(1f, scale, px, py)
    transform(matrix)
}

fun Path.scaleX(scale: Float, point: Point) {
    val matrix = Matrix()
    matrix.scale(scale, 1f, px, py)
    transform(matrix)
}

fun Path.scaleY( scale: Float, point: Point) {
    val matrix = Matrix()
    matrix.
    matrix.scale(1f, scale, px, py)
    transform(matrix)
}*/


/**
 * Scales this path to [width] and [height] from [Path.getBounds] and translates
 * as difference between scaled path and original path
 */
fun Path.scaleAndTranslatePath(
    width: Float,
    height: Float,
) {
    val pathSize = getBounds().size

    val matrix = Matrix()
    /*matrix.postScale(
        width / pathSize.width,
        height / pathSize.height
    )
    this.asAndroidPath().transform(matrix)*/

    matrix.scale(width / pathSize.width,
        height / pathSize.height)
    transform(matrix)

    val left = getBounds().left
    val top = getBounds().top

    translate(Offset(-left, -top))
}


fun Path.addRoundedRect(
    radiusTopLeft: Float,
    radiusTopRight: Float,
    radiusBottomRight: Float,
    radiusBottomLeft: Float,
    topLeft: Offset,
    size: Size
) {
    val topLeftRadius = radiusTopLeft * 2
    val topRightRadius = radiusTopRight * 2
    val bottomRightRadius = radiusBottomRight * 2
    val bottomLeftRadius = radiusBottomLeft * 2

    val width = size.width
    val height = size.height

    // Top left arc
    arcTo(
        rect = Rect(
            left = topLeft.x,
            top = topLeft.y,
            right = topLeft.x + topLeftRadius,
            bottom = topLeft.y + topLeftRadius
        ),
        startAngleDegrees = 180.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = topLeft.x + width - topRightRadius, y = topLeft.y)

    // Top right arc
    arcTo(
        rect = Rect(
            left = topLeft.x + width - topRightRadius,
            top = topLeft.y,
            right = topLeft.x + width,
            bottom = topLeft.y + topRightRadius
        ),
        startAngleDegrees = -90.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = topLeft.x + width, y = topLeft.y + height - bottomRightRadius)

    // Bottom right arc
    arcTo(
        rect = Rect(
            left = topLeft.x + width - bottomRightRadius,
            top = topLeft.y + height - bottomRightRadius,
            right = topLeft.x + width,
            bottom = topLeft.y + height
        ),
        startAngleDegrees = 0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = topLeft.x + bottomLeftRadius, y = topLeft.y + height)

    // Bottom left arc
    arcTo(
        rect = Rect(
            left = topLeft.x,
            top = topLeft.y + height - bottomLeftRadius,
            right = topLeft.x + bottomLeftRadius,
            bottom = topLeft.y + height
        ),
        startAngleDegrees = 90.0f,
        sweepAngleDegrees = 90.0f,
        forceMoveTo = false
    )

    lineTo(x = topLeft.x, y = topLeft.y + topLeftRadius)
    close()
}

fun Path.addPointsLine(points: List<Offset>){
    if (points.size > 1) {
        var oldPoint: Offset? = null
        this.moveTo(points[0].x, points[0].y)
        for (i in 1 until points.size) {
            val point: Offset = points[i]
            oldPoint?.let {
                val midPoint = calculateMidpoint(it, point)
                if (i == 1) {
                    lineTo(midPoint.x, midPoint.y)
                } else {
                    quadraticTo(it.x, it.y, midPoint.x, midPoint.y)
                }
            }
            oldPoint = point
        }
        oldPoint?.let { this.lineTo(it.x, oldPoint.y) }
    }
}

private fun calculateMidpoint(start: Offset, end: Offset) =
    Offset((start.x + end.x) / 2, (start.y + end.y) / 2)


